//
//  IfBindMultipleScrollView.m
//  testDisplaylink
//
//  Created by Wang Xiahua on 15-5-27.
//  Copyright (c) 2015年 SW. All rights reserved.
//

#import "IfBindMultipleScrollView.h"

@implementation IfBindMultipleScrollView{
  NSMutableArray* _bindScrollViews;
  UIScrollView* _syncSource;
  BOOL _isSettingTopWebViewContentSize;
}

- (void)dealloc {
  NSArray* array = [_bindScrollViews copy];
  for (UIScrollView* element in array){
    [self unbindScrollView:element];
  }
  self.topWebView = nil;
}

- (instancetype)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self){
    _bindScrollViews = [NSMutableArray array];
  }
  return self;
}

- (void)setTopWebView:(UIWebView *)topWebView {
  if (_topWebView != topWebView){
    if (_topWebView){
      [_topWebView.scrollView removeObserver:self forKeyPath:@"contentOffset"];
      [_topWebView.scrollView removeObserver:self forKeyPath:@"contentSize"];
      [_topWebView removeFromSuperview];
    }

    _topWebView = topWebView;
    if (_topWebView){
      [self addSubview:_topWebView];
      _topWebView.frame = self.bounds;
      _topWebView.autoresizingMask =
        UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
      _topWebView.backgroundColor = [UIColor clearColor];

      _topWebView.scrollView.backgroundColor = [UIColor clearColor];
      _topWebView.scrollView.showsHorizontalScrollIndicator = NO;

      _topWebViewOriginScrollViewSize = _topWebView.scrollView.contentSize;
      CGSize size = self.contentSize;
      size.height += _topWebViewOriginScrollViewSize.height;
      self.contentSize = size;

      [_topWebView.scrollView addObserver:self forKeyPath:@"contentOffset"
                                  options:0 context:&_topWebView];
      [_topWebView.scrollView addObserver:self forKeyPath:@"contentSize"
                                  options:0 context:&_topWebView];
    }
  }
}

- (void)addBindScrollView:(UIScrollView *)scrollView {
  UIView* view = scrollView;
  // if view is not add into view hierarchy, get the top view as view;
  if (!view.window)
    while (view.superview) view = view.superview;
  [self addView:view andBindScrollView:scrollView];
}

- (void)addView:(UIView *)view andBindScrollView:(UIScrollView *)scrollView {
  view.frame = self.bounds;
  view.autoresizingMask =
    UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
  // clearColor so won't hide other scrollView;
  view.backgroundColor = [UIColor clearColor];
  [self addSubview:view];

  [self bindScrollView:scrollView];
}

- (void)bindScrollView:(UIScrollView*)scrollView {
  if ([_bindScrollViews containsObject:scrollView]) return;

  scrollView.showsHorizontalScrollIndicator = NO;

  [_bindScrollViews addObject:scrollView];

  CGSize size = self.contentSize;
  size.height += scrollView.contentSize.height;
  self.contentSize = size;

  [scrollView addObserver:self forKeyPath:@"contentSize"
                  options:NSKeyValueObservingOptionOld
                  context:(void*)scrollView];
  [scrollView addObserver:self forKeyPath:@"contentOffset"
                  options:0 context:(void*)scrollView];
  // [scrollView addObserver:self forKeyPath:@"contentInset" options:NSKeyValueObservingOptionOld | NSKeyValueObservingOptionNew context:(void*)scrollView];
}

- (void)unbindScrollView:(UIScrollView*)scrollView {
  NSInteger index = [_bindScrollViews indexOfObject:scrollView];
  if (index != NSNotFound) {
    [scrollView removeObserver:self forKeyPath:@"contentSize"];
    [scrollView removeObserver:self forKeyPath:@"contentOffset"];
    // [scrollView removeObserver:self forKeyPath:@"contentInset"];
    [_bindScrollViews removeObjectAtIndex:index];
  }
}

- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  if (context){
     // if ([keyPath isEqualToString:@"contentInset"]) {
     //   NSLog(@"contentInset %@", change);
     // }
     // NSLog(@"observer %@: %@", keyPath, object);

    UIScrollView* scrollView = object;
    if ([keyPath isEqualToString:@"contentOffset"]) {
      [self syncOffset:scrollView];
    } else if ([keyPath isEqualToString:@"contentSize"]) {
      CGSize oldSize;
      if (context == &_topWebView){
        /* here should ensure it's the webView change contentSize itself

         sometimes may re set contentSize, so ignore it
         but this may have problem when really change to the contentSize
         so and judge inner UIWebBrowserView(first subview) height */
        CGFloat height;
        UIScrollView* scrollView;
        if (_isSettingTopWebViewContentSize ||
            (scrollView = _topWebView.scrollView,
             height = scrollView.contentSize.height,

             height == _contentSize.height && scrollView.subviews.count > 0 &&
             // UIWebBrowserView.height should equal to contentSize.height if
             // set by webView;
             height != [scrollView.subviews[0] bounds].size.height
            ))
          return;
        oldSize = _topWebViewOriginScrollViewSize;
        _topWebViewOriginScrollViewSize = _topWebView.scrollView.contentSize;
      }else
        oldSize = [change[NSKeyValueChangeOldKey] CGSizeValue];

      CGSize newSize = scrollView.contentSize;
      CGFloat changeHeight = newSize.height - oldSize.height;
      if (changeHeight == 0 && context != &_topWebView) return;
      // NSLog(@"newSize {%f,%f}", newSize.width, newSize.height);
      newSize = self.contentSize;
      newSize.height += changeHeight;
      self.contentSize = newSize; // will sync inset in setter method
    }
    return;
  }
  return [super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}

- (void)syncOffset:(UIScrollView*)syncSource {
  if (_syncSource) return;
  _syncSource = syncSource;
  CGFloat offsetY = syncSource.contentOffset.y + syncSource.contentInset.top;
  // NSLog(@"offsetY, %f", offsetY);
  CGPoint point;
  for (UIScrollView* element in _bindScrollViews){
    if (element == syncSource) continue;
    // sync contentOffset
    point = element.contentOffset;
    point.y = offsetY - element.contentInset.top;
    element.contentOffset = point;
  }
  if (syncSource != _topWebView.scrollView){
    point = _topWebView.scrollView.contentOffset;
    point.y = offsetY;
    _topWebView.scrollView.contentOffset = point;
  }
  _syncSource = nil;
}

- (void)setContentSize:(CGSize)contentSize {
  _contentSize = contentSize;
  [self resetInsetInScrollViews];
}

- (void)resetInsetInScrollViews {
  if (_bindScrollViews.count > 0) {
    // set syncSource to prevent sync offset here
    _syncSource = _topWebView?_topWebView.scrollView : _bindScrollViews[0];
  }
  UIEdgeInsets inset;
  CGFloat top = 0;
  if (_topWebView){ // topWebView use contentSize instead inset
    _isSettingTopWebViewContentSize = YES;
    _topWebView.scrollView.contentSize = _contentSize;
    _isSettingTopWebViewContentSize = NO;
    top = _topWebViewOriginScrollViewSize.height;
  }

  for (UIScrollView* element in _bindScrollViews){
    inset = element.contentInset;
    inset.top = top;
    top += element.contentSize.height;
    inset.bottom = _contentSize.height - top;
    element.contentInset = inset;
  }
  // resync offset after change inset
  if (_syncSource){
    UIScrollView* scrollView = _syncSource;
    _syncSource = nil;
    [self syncOffset:scrollView];
  }
}

- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
  CGPoint localPoint;
  NSArray* scrollViews;
  if (_topWebView){
    NSMutableArray* array = [NSMutableArray arrayWithArray:_bindScrollViews];
    // _topWebView.scrollView must be the last, because it's contentSize is full
    [array addObject:_topWebView.scrollView];
    scrollViews = array;
  }else{
    scrollViews = _bindScrollViews;
  }

  for (UIScrollView* element in scrollViews){
    localPoint = [element convertPoint:point fromView:self];
    if (localPoint.y > 0 && localPoint.y < element.contentSize.height) {
      // bringToFront so something like scrollBar won't hide
      UIView* view = element;
      while (view.superview != self) view = view.superview;
      [self bringSubviewToFront:view];
      break;
      // return [element hitTest:localPoint withEvent:event];
    }
  }
  return [super hitTest:point withEvent:event];
}

@end
